﻿using System;
using Apache.NMS;
using Apache.NMS.ActiveMQ;
using Apache.NMS.ActiveMQ.Commands;
using log4net;
using Reactor.Environment;
using Reactor.Messaging;
using Reactor.Messaging.Exceptions;
using Reactor.Messaging.ServiceBus;

namespace Reactor.ServiceBus.Transports.ActiveMQ
{
    /// <summary>
    /// Encapsulates the creation and lifetime management of a JMS Connection. Also responsible for verifying connection status 
    /// and providing Session instances when needed.
    /// </summary>
    public class ActiveMqGateway : INmsGateway
    {
        #region Fields

        private static readonly ILog Log = LogManager.GetLogger(typeof (INmsGateway));
        private readonly IConnectionFactory _connectionFactory;
        private readonly BrokerConnectionDetails _brokerConnectionDetails;
        private IConnection _connection;
        private static readonly object ConnectionLock = new object();

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="ActiveMqGateway"/> class.
        /// </summary>
        /// <param name="brokerConnectionDetails">The connection data.</param>
        public ActiveMqGateway(BrokerConnectionDetails brokerConnectionDetails) : this(new ConnectionFactory(), brokerConnectionDetails) {}

        /// <summary>
        /// Initializes a new instance of the <see cref="ActiveMqGateway"/> class.
        /// </summary>
        /// <param name="connectionFactory">The connection factory.</param>
        /// <param name="brokerConnectionDetails">The connection data.</param>
        public ActiveMqGateway(IConnectionFactory connectionFactory, BrokerConnectionDetails brokerConnectionDetails)
        {
            if (connectionFactory == null) throw new ArgumentNullException("connectionFactory");
            if (brokerConnectionDetails == null) throw new ArgumentNullException("brokerConnectionDetails");

            _connectionFactory = connectionFactory;
            _brokerConnectionDetails = brokerConnectionDetails;

            _connectionFactory.BrokerUri = new Uri(brokerConnectionDetails.Uri);
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Starts this instance by creating a <seealso cref="Connection"/> and starting it.
        /// </summary>
        public void Startup()
        {
            lock (ConnectionLock)
            {
                try
                {
                    _connection = _connectionFactory.CreateConnection(_brokerConnectionDetails.User, _brokerConnectionDetails.Password);
                    _connection.ConnectionInterruptedListener += OnConnectionInterrupted;
                    _connection.Start();
                }
                catch (NMSConnectionException e)
                {
                    throw new ConnectionException(e.Message, e);
                }
            }
        }

        /// <summary>
        /// Shuts down this instance by closing the underlying JMS Connection.
        /// </summary>
        public void Shutdown()
        {
            lock (ConnectionLock)
            {
                if (_connection == null) return;

                _connection.Close();
                _connection = null;
            }
        }

        /// <summary>
        /// Attempts to delete the specified MessagingChannel. If the MessagingChannel was successfully deleted, a <c>true</c> value 
        /// is returned. If not, <c>false</c> is returned. A possible reason for failure is if the MessagingChannel currently 
        /// has attached consumers.
        /// </summary>
        /// <param name="messagingChannel">The MessagingChannel.</param>
        /// <returns><c>true</c> if the MessagingChannel was successfully deleted. <c>false</c> if the deletion failed.</returns>
        public bool DeleteDestination(IMessagingChannel messagingChannel)
        {
            lock (ConnectionLock)
            {
                if (_connection == null) return false;

                try
                {
                    var conn = (Connection)_connection;
                    var nmsDestination = (messagingChannel is PointToPointChannel)
                                                      ? (Apache.NMS.IDestination)new ActiveMQQueue(messagingChannel.DestinationName)
                                                      : new ActiveMQTopic(messagingChannel.DestinationName);

                    conn.DeleteDestination(nmsDestination);

                    return true;
                }
                catch (Exception ex)
                {
                    Log.Error(string.Format("Exception thrown while attempting to delete MessagingChannel: {0}", messagingChannel.GetTypedDestinationName()), ex);

                    return false;
                }
            }
        }

        /// <summary>
        /// Factory method to create and get a new JMS Session instance.
        /// </summary>
        /// <returns></returns>
        public ISession GetSession()
        {
            lock (ConnectionLock)
            {
                if (_connection == null || !_connection.IsStarted)
                    throw new ConnectionException("Connection not created or not started. Start instance prior to requesting Session instances.");

                return _connection.CreateSession(AcknowledgementMode.AutoAcknowledge);
            }
        }

        /// <summary>
        /// Occurs when the unerlying connection is interrupted.
        /// </summary>
        public event EventHandler ConnectionInterrupted;

        #endregion

        private void OnConnectionInterrupted()
        {
            if (ConnectionInterrupted != null)
                ConnectionInterrupted(this, EventArgs.Empty);
        }
    }
}
